package net.jonbuck.tassoo.persistence.dao;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import net.jonbuck.tassoo.model.AttachmentType;
import net.jonbuck.tassoo.model.AttachmentTypes;
import net.jonbuck.tassoo.model.Categories;
import net.jonbuck.tassoo.model.Category;
import net.jonbuck.tassoo.model.Comment;
import net.jonbuck.tassoo.model.Container;
import net.jonbuck.tassoo.model.Containers;
import net.jonbuck.tassoo.model.Priorities;
import net.jonbuck.tassoo.model.Priority;
import net.jonbuck.tassoo.model.Status;
import net.jonbuck.tassoo.model.Statuses;
import net.jonbuck.tassoo.model.Task;
import net.jonbuck.tassoo.model.Tassoo;
import net.jonbuck.tassoo.model.TassooFactory;
import net.jonbuck.tassoo.model.TassooPackage;
import net.jonbuck.tassoo.model.Type;
import net.jonbuck.tassoo.model.Types;
import net.jonbuck.tassoo.model.util.TassooAdapterFactory;
import net.jonbuck.tassoo.eventmanager.EventManager;
import net.jonbuck.tassoo.eventmanager.EventManagerImpl;
import net.jonbuck.tassoo.eventmanager.events.ContainerCreationEvent;
import net.jonbuck.tassoo.eventmanager.events.TaskStoreClosedEvent;
import net.jonbuck.tassoo.eventmanager.events.TaskStoreOpenedEvent;
import net.jonbuck.tassoo.persistence.Activator;
import net.jonbuck.tassoo.persistence.exception.TassooCreationException;
import net.jonbuck.tassoo.persistence.exception.TassooSaveException;
import net.jonbuck.tassoo.persistence.nl.Messages;
import net.jonbuck.tassoo.persistence.services.TaskStoreState;

import org.apache.commons.lang.StringUtils;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.services.ISourceProviderService;

/**
 * <p>
 * <b>Persistence implementation of the TassooModel.</b> This class provides the
 * application with access to the data stored in the model. It is responsible
 * for loading, persisting and managing the model that backs the application.
 * The class also manages the initial creation of the model and the population
 * of any data required for the application to function such as creating
 * default, categories, priorities, statuses and types.
 * </p>
 * 
 * @since 1.0.0
 */
public class TassooDaoImpl implements TassooDao {

	/** ... Outstanding Comment... */
	private static final String DOT = "."; //$NON-NLS-1$

	/** ... Outstanding Comment... */
	private static final String FILE_EXTENSION = "tassoo"; //$NON-NLS-1$

	/** ... Outstanding Comment... */
	private static TassooDao instance;

	/**
	 * 
	 * @return
	 */
	public static TassooDao getInstance() {
		if (instance == null) {
			instance = new TassooDaoImpl();
		}
		return instance;
	}

	/** ... Outstanding Comment... */
	protected AdapterFactoryEditingDomain editingDomain;

	/** ... Outstanding Comment... */
	private EventManager eventManager = EventManagerImpl.getInstance();

	/** ... Outstanding Comment... */
	private IPreferenceStore preferenceStore;

	/** ... Outstanding Comment... */
	private Resource resource;

	/** ... Outstanding Comment... */
	private URI rootfileURI;

	/** ... Outstanding Comment... */
	private SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy, HH:mm");

	/** ... Outstanding Comment... */
	private String systemDir;

	/** ... Outstanding Comment... */
	private TassooFactory tassooFactory;

	/** ... Outstanding Comment... */
	private TassooPackage tassooPackage;

	/**
	 * <p>
	 * <b>Default constructor.</b>
	 * </p>
	 */
	private TassooDaoImpl() {
		super();
		bootstrap();
	}

	/**
	 * 
	 */
	public void addModelListener(Adapter adapter) {
		if (resource != null && resource.isLoaded()) {
			EList<EObject> contents = resource.getContents();
			Tassoo tassoo = (Tassoo) contents.get(0);
			tassoo.getContainers().eAdapters().add(adapter);
		}
	}

	/**
	 * <p>
	 * <b>Bootstraps the data when the application is first loaded.</b>That is
	 * this method determines whether the data has been created and if not will
	 * manage the creation of the base objects and default data.
	 * </p>
	 */
	public void bootstrap() {
		setSystemDir();
		tassooPackage = TassooPackage.eINSTANCE;
		tassooFactory = tassooPackage.getTassooFactory();
		editingDomain = new AdapterFactoryEditingDomain(new TassooAdapterFactory(), new BasicCommandStack(),
				new HashMap<Resource, Boolean>());
		preferenceStore = Activator.getDefault().getPreferenceStore();
	}

	/**
	 * <p>
	 * <b>Closes the currently loaded task store.</b>
	 * </p>
	 */
	public boolean closeTaskStore(boolean saveTaskStore) {

		if (saveTaskStore) {
			this.saveTaskStore();
		}

		resource.unload();

		ISourceProviderService sourceProviderService = (ISourceProviderService) PlatformUI.getWorkbench().getService(
				ISourceProviderService.class);
		TaskStoreState taskStoreStateService = (TaskStoreState) sourceProviderService.getSourceProvider(TaskStoreState.MY_STATE);
		taskStoreStateService.checkState();

		eventManager.fireEvent(new TaskStoreClosedEvent());

		return true;
	}

	/**
	 * 
	 */
	public void createComment(Task task, String text) {
		Comment comment = tassooFactory.createComment();
		comment.setDate(sdf.format(new Date()));
		comment.setText(text);
		task.getComment().add(comment);
	}

	/**
	 * 
	 * TODO Validation here against the model..
	 */
	public void createNewContainer(Container container) throws TassooCreationException {
		Containers containers = this.getContainers();
		containers.getContainers().add(container);

		eventManager.fireEvent(new ContainerCreationEvent(container));
	}

	/**
	 * 
	 */
	public void createNewTask(Task newTask, Container parentContainer) {
		parentContainer.getTask().add(newTask);
	}

	/**
	 * 
	 */
	public void createNewTask(Task newTask, Task parentTask) {
		parentTask.getTask().add(newTask);

	}

	/**
	 * <p>
	 * <b>Creates a new <em>Tassoo</em> task store file using the values passed
	 * in.<b>
	 * </p>
	 */
	public boolean createNewTaskStore(Tassoo tassoo) throws TassooCreationException, TassooSaveException {
		File rootfile = new File(tassoo.getFilePath());
		if (rootfile.isDirectory()) {
			URI rootfileURI = null;
			if (StringUtils.endsWith(tassoo.getFilePath(), File.separator)) {
				rootfileURI = URI.createFileURI(rootfile.getAbsolutePath().concat(File.separator).concat(tassoo.getName())
						.concat(DOT).concat(FILE_EXTENSION));
			} else {
				rootfileURI = URI.createFileURI(rootfile.getAbsolutePath().concat(tassoo.getName()).concat(DOT)
						.concat(FILE_EXTENSION));
			}
			resource = editingDomain.getResourceSet().createResource(rootfileURI);

			EList<EObject> contents = resource.getContents();
			Tassoo rootObject = (Tassoo) tassooFactory.createTassoo();
			rootObject.setDescription(tassoo.getDescription());

			rootObject.setAttachmentTypes(generateDefaultAttachmentTypes());
			rootObject.setCategories(generateDefaultCategories());
			rootObject.setPriorities(generateDefaultPriorities());
			rootObject.setStatuses(generateDefaultStatuses());
			rootObject.setTypes(generateDefaultTypes());
			rootObject.setContainers(generateDefaultContainers());

			contents.add(rootObject);
			this.saveTaskStore();

			ISourceProviderService sourceProviderService = (ISourceProviderService) PlatformUI.getWorkbench().getService(
					ISourceProviderService.class);
			TaskStoreState taskStoreStateService = (TaskStoreState) sourceProviderService
					.getSourceProvider(TaskStoreState.MY_STATE);
			taskStoreStateService.checkState();

			eventManager.fireEvent(new TaskStoreOpenedEvent());

			return true;
		} else {
			throw new TassooCreationException();
		}
	}

	/**
	 * TODO: Finish implementation for default attachment types, also need to
	 * take into account the commands for different platforms such as windows,
	 * Linux, Mac OSX etc.
	 * 
	 * @return
	 */
	private AttachmentTypes generateDefaultAttachmentTypes() {
		AttachmentTypes attachmentTypes = tassooFactory.createAttachmentTypes();
		AttachmentType writerType = tassooFactory.createAttachmentType();
		writerType.setCommand("/c writer");
		writerType.setIcon("");
		writerType.setName(Messages.attachmenttypes_openofficewriter);
		attachmentTypes.getAttachmentType().add(writerType);
		return attachmentTypes;
	}

	/**
	 * <p>
	 * <b>Helper method which will build the list of default categories that the
	 * user can base their own list on.</b>
	 * </p>
	 * 
	 * TODO Think about some way of externalizing this data so that the method
	 * is not hard coded.
	 * 
	 * @return Categories a <code>Categories</code> object containing the list
	 *         of <code>Category</code> objects that are the default list of
	 *         categories.
	 */
	private Categories generateDefaultCategories() {
		Categories categories = tassooFactory.createCategories();

		Category personalCategory = tassooFactory.createCategory();
		personalCategory.setValue("Personal");
		categories.getCategory().add(personalCategory);

		Category projectsCategory = tassooFactory.createCategory();
		projectsCategory.setValue("Projects");
		categories.getCategory().add(projectsCategory);

		Category workCategory = tassooFactory.createCategory();
		workCategory.setDefault(true);
		workCategory.setValue("Work");
		categories.getCategory().add(workCategory);

		return categories;
	}

	/**
	 * 
	 * @return
	 */
	private Containers generateDefaultContainers() {
		Containers containers = tassooFactory.createContainers();
		Container container = tassooFactory.createContainer();
		container.setContainerName("My To-Do List");
		container.setContainerDescription("Default Container generated by Tassoo");
		containers.getContainers().add(container);
		return containers;
	}

	/**
	 * <p>
	 * <b>Helper method which will build the list of default priorities that the
	 * user can base their own list on.</b>
	 * </p>
	 * 
	 * TODO Think about some way of externalizing this data so that the method
	 * is not hard coded.
	 * 
	 * @return Priorities a <code>Priorities</code> object containing the list
	 *         of <code>Priority</code> objects that are the default list of
	 *         priorities.
	 */
	private Priorities generateDefaultPriorities() {
		Priorities priorities = tassooFactory.createPriorities();
		Priority highPriority = tassooFactory.createPriority();
		highPriority.setValue("High");
		priorities.getPriority().add(highPriority);
		Priority mediumPriority = tassooFactory.createPriority();
		mediumPriority.setDefault(true);
		mediumPriority.setValue("Medium");
		priorities.getPriority().add(mediumPriority);
		Priority lowPriority = tassooFactory.createPriority();
		lowPriority.setValue("Low");
		priorities.getPriority().add(lowPriority);
		return priorities;
	}

	/**
	 * <p>
	 * <b>Helper method which will build the list of default statuses that the
	 * user can base their own list on.</b>
	 * </p>
	 * 
	 * TODO Think about some way of externalizing this data so that the method
	 * is not hard coded.
	 * 
	 * @return Statuses a <code>Statuses</code> object containing the list of
	 *         <code>Status</code> objects that are the default list of
	 *         statuses.
	 */
	private Statuses generateDefaultStatuses() {
		Statuses statuses = tassooFactory.createStatuses();
		Status newStatus = tassooFactory.createStatus();
		newStatus.setDefault(true);
		newStatus.setValue("New");
		newStatus.setAssociatedPercentage(0);
		statuses.getStatus().add(newStatus);
		Status plannedStatus = tassooFactory.createStatus();
		plannedStatus.setValue("Planned");
		plannedStatus.setAssociatedPercentage(20);
		statuses.getStatus().add(plannedStatus);
		Status inprogressStatus = tassooFactory.createStatus();
		inprogressStatus.setValue("In Progress");
		inprogressStatus.setAssociatedPercentage(30);
		statuses.getStatus().add(inprogressStatus);
		Status doneStatus = tassooFactory.createStatus();
		doneStatus.setValue("Done");
		doneStatus.setAssociatedPercentage(100);
		statuses.getStatus().add(doneStatus);
		return statuses;
	}

	/**
	 * <p>
	 * <b>Helper method which will build the list of default types that the user
	 * can base their own list on.</b>
	 * </p>
	 * 
	 * TODO Think about some way of externalizing this data so that the method
	 * is not hard coded.
	 * 
	 * @return Types a <code>Types</code> object containing a list of
	 *         <code>Type</code> objects that are the default list of types.
	 */
	private Types generateDefaultTypes() {
		Types types = tassooFactory.createTypes();
		Type taskType = tassooFactory.createType();
		taskType.setDefault(true);
		taskType.setValue("Task");
		types.getType().add(taskType);
		return types;
	}

	/**
	 * 
	 */
	public AttachmentTypes getAttachmentTypes() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getAttachmentTypes();
	}

	/**
	 * 
	 */
	public Categories getCategories() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getCategories();
	}

	/**
	 * 
	 */
	public Containers getContainers() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getContainers();
	}

	/**
	 * 
	 */
	public AdapterFactoryEditingDomain getEditingDomain() {
		return editingDomain;
	}

	/**
	 * 
	 */
	public Priorities getPriorities() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getPriorities();
	}

	/**
	 * 
	 */
	public Statuses getStatuses() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getStatuses();
	}

	public String getSystemDir() {
		return systemDir;
	}

	/**
	 * 
	 * @return
	 */
	public Tassoo getTassoo() {
		EList<EObject> contents = resource.getContents();
		return (Tassoo) contents.get(0);
	}

	/**
	 * 
	 */
	public Types getTypes() {
		EList<EObject> contents = resource.getContents();
		Tassoo tassoo = (Tassoo) contents.get(0);
		return tassoo.getTypes();
	}

	/**
	 * 
	 * @return
	 */
	public boolean isLoaded() {
		if (resource != null && resource.isLoaded()) {
			return true;
		}
		return false;
	}

	/**
	 * 
	 */
	public boolean loadTaskStore(String filePath) {
		File rootfile = new File(filePath);
		rootfileURI = URI.createFileURI(rootfile.getAbsolutePath());
		if (new File(rootfileURI.toFileString()).exists()) {
			resource = editingDomain.getResourceSet().getResource(rootfileURI, true);
		}

		ISourceProviderService sourceProviderService = (ISourceProviderService) PlatformUI.getWorkbench().getService(
				ISourceProviderService.class);
		TaskStoreState taskStoreStateService = (TaskStoreState) sourceProviderService.getSourceProvider(TaskStoreState.MY_STATE);
		taskStoreStateService.checkState();

		eventManager.fireEvent(new TaskStoreOpenedEvent());

		return true;
	}

	/**
	 * 
	 */
	public void removeModelListener(Adapter adapter) {
		if (resource != null && resource.isLoaded()) {
			EList<EObject> contents = resource.getContents();
			Tassoo tassoo = (Tassoo) contents.get(0);
			tassoo.eAdapters().remove(adapter);
		}
	}

	/**
	 * 
	 */
	public boolean saveTaskStore() throws TassooSaveException {
		Map<Object, Object> options = new HashMap<Object, Object>();
		options.put(XMLResource.OPTION_ENCODING, "UTF-8");
		try {
			resource.save(options);
		} catch (IOException e) {
			throw new TassooSaveException(e);
		}
		return true;
	}

	/**
	 * 
	 */
	private void setSystemDir() {
		String userHomeDir = System.getProperty("user.home", ".");
		systemDir = userHomeDir.concat(File.separator).concat(".tassoo").concat(File.separator);

		// create the system directory if it doesn't exist
		File fileSystemDir = new File(systemDir);
		if (!fileSystemDir.exists()) {
			fileSystemDir.mkdir();
		}
	}

	public void setSystemDir(String systemDir) {
		this.systemDir = systemDir;
	}

	/**
	 * 
	 */
	public void shutdown() {
		if (this.isLoaded()) {
			preferenceStore.putValue("net.jonbuck.tassoo.lastfileopened", rootfileURI.toFileString());
		}
	}

}
